#region Copyright Notices
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Library General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Library General Public License for more details.
// 
// You should have received a copy of the GNU Library General Public
// License along with this library; if not, write to the Free
// Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#endregion

using System;
using System.Collections.Generic;

namespace Yarr
{
	/// <summary>
	/// The ReadTable is a lookup table which directs the reader on
	/// how to react to the presence of certain character combinations
    /// in the input stream. 
    /// 
    /// By default, a readtable containing the default built-in reader 
    /// macros is created by Environment.MakeEnvironment, and bound to 
    /// the *readtable* symbol. 
    /// 
    /// It is also possible for hosting applications to create their own 
    /// custom readtables.
	/// </summary>
	public class ReadTable
    {
        #region Private members
        private const int CAPACITY = 50;
		private Dictionary<Char, Object> readTable = new Dictionary<Char, Object>(CAPACITY);
        #endregion


        #region Constructors
        internal ReadTable() { }
        #endregion


        #region Static methods
        /// <summary>
        /// Creates and returns an empty readtable. It is up to the hosting 
        /// application to populate the readtable with reader macros and 
        /// dispatch reader macros, and bind it to the *readtable* symbol in 
        /// an environment.
        /// </summary>
        /// <returns>An empty ReadTable instance</returns>
        public static ReadTable MakeReadTable()
        {
            return new ReadTable();
        }

        /// <summary>
        /// Makes a copy of <paramref name="readtable"/>, containing all of the reader macros
        /// and dispatch reader macros of the original, and bound to the same
        /// characters as in <paramref name="readtable"/>
        /// </summary>
        /// <param name="readtable">The readtable to copy</param>
        /// <returns>A new readtable with the same entries as the one provided</returns>
        public static ReadTable MakeReadTable (ReadTable readtable)
        {
            ReadTable newtable = new ReadTable();

            lock (readtable)
            {
                foreach (Char key in readtable.readTable.Keys)
                {
                    if (readtable.readTable[key] is Dictionary<Char, Object>)
                    {
                        Dictionary<Char, Object> oldDisp = (Dictionary<Char, Object>)readtable.readTable[key];
                        Dictionary<Char, Object> dispatch = new Dictionary<Char, Object>(CAPACITY);
                        newtable.readTable[key] = dispatch;
                        foreach (Char subkey in oldDisp.Keys)
                            dispatch[subkey] = oldDisp[subkey];
                    }
                    else
                        newtable.readTable[key] = readtable.readTable[key];
                }
            }

            return newtable;
        }
        #endregion


        #region Public methods
        /// <summary>
        /// If the specified <paramref name="character"/> is assigned to a dispatch reader, 
        /// obtains the dispatch reader macro for the character <paramref name="dispatch"/> 
        /// in that dispatch reader.
        /// </summary>
        /// <param name="character">A character with a dispatch reader</param>
        /// <param name="dispatch">The character for which to obtain the dispatch reader macro</param>
        /// <returns>The dispatch reader macro</returns>
        public Object GetDispatchCharacter(int character, int dispatch)
        {
            lock (readTable)
            {
                return (readTable.ContainsKey((Char)character) &&
                            (readTable[(Char)character] is Dictionary<Char, Object>)) ?
                                (((Dictionary<Char, Object>)readTable[(Char)character])[(Char)dispatch])
                                : null;
            }
        }

        /// <summary>
        /// Obtain the reader macro for a specified <paramref name="character" />
        /// </summary>
        /// <param name="character">The character for which to obtain the reader macro</param>
        /// <returns>The reader macro for the character</returns>
        public Object GetMacroCharacter(int character)
        {
            lock (readTable)
            {
                return readTable.ContainsKey((Char)character) ? readTable[(Char)character] : null;
            }
        }

        /// <summary>
        /// Assigns the specified <paramref name="character"/> to a dispatch reader, 
        /// then assigns the DispatchReaderMacro <paramref name="macro"/> as the dispatch reader macro 
        /// for the character <paramref name="dispatch"/> in that dispatch reader.
        /// </summary>
        /// <param name="character">The character to process with a dispatch reader</param>
        /// <param name="dispatch">The character for which to define a dispatch reader macro</param>
        /// <param name="macro">The dispatch reader macro</param>
        public void SetDispatchCharacter(int character, int dispatch, DispatchReaderMacro macro)
        {
            SetDispatchCharacter((Char)character, (Char)dispatch, (object)macro);
        }

        /// <summary>
        /// Assigns the specified <paramref name="character"/> to a dispatch reader, 
        /// then assigns the Yarr.Closure <paramref name="closure"/> as the dispatch reader macro 
        /// for the character <paramref name="dispatch"/> in that dispatch reader.
        /// </summary>
        /// <param name="character">The character to process with a dispatch reader</param>
        /// <param name="dispatch">The character for which to define a dispatch reader macro</param>
        /// <param name="closure">The Yarr closure to use as the dispatch reader macro</param>
        public void SetDispatchCharacter(Char character, Char dispatch, Closure closure)
        {
            SetDispatchCharacter(character, dispatch, (object)closure);
        }

        /// <summary>
        /// Assigns the ReaderMacro <paramref name="macro"/> as the dispatch reader macro
        /// for the character <paramref name="character"/>
        /// </summary>
        /// <param name="character">The character for which to define a reader macro</param>
        /// <param name="macro">The reader macro</param>
		public void SetMacroCharacter(int character, ReaderMacro macro) 
		{
            lock (readTable)
            {
                readTable[(Char)character] = macro;
            }
        }

        /// <summary>
        /// Assigns the Yarr.Closure <paramref name="closure"/> as the dispatch reader macro
        /// for the character <paramref name="character"/>
        /// </summary>
        /// <param name="character">The character for which to define a reader macro</param>
        /// <param name="closure">The Yarr closure to use as the reader macro</param>
        public void SetMacroCharacter (Char character, Closure closure)
        {
            lock (readTable)
            {
                readTable[character] = closure;
            }
        }
        #endregion


        #region Helper methods
        internal Dictionary<Char, Object> GetDispatcher (int c)
        {
            lock (readTable)
            {
                return (readTable.ContainsKey((Char)c) &&
                        (readTable[(Char)c] is Dictionary<Char, Object>)) ?
                            (Dictionary<Char, Object>)readTable[(Char)c] : null;
            }
        }

        internal void SetDispatchCharacter (Char c, Char sub, Object f)
		{
            Dictionary<Char, Object> dispatchTable;

            bool containsKey;
            lock (readTable)
            {
                containsKey = readTable.ContainsKey(c);
            }

            if (containsKey)
            {
                lock (readTable)
                {
                    dispatchTable = (Dictionary<Char, Object>)readTable[c];
                }
            }
            else
            {
                dispatchTable = new Dictionary<Char, Object>(CAPACITY);

                lock (readTable)
                {
                    readTable[c] = dispatchTable;
                }
            }

            lock (dispatchTable)
            {
                dispatchTable[sub] = f;
            }
        }
        #endregion
    }
}
